<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue双向数据绑定</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="text" /> {{text}}
  </div>
</body>

<script>
  //  编译函数
  function compile(node, vm) {
    const reg = /\{\{(.*)\}\}/; // 来匹配{{xxx}}中的xxx
    //  如果是元素节点的话
    if (node.nodeType === 1) {
      const attr = node.attributes
      //  解析元素节点的所有属性
      for (let i = 0; i < attr.length; i++) {
        // 判断属性名称是不是 v-model
        if (attr[i].nodeName == 'v-model') {
          const name = attr[i].nodeValue // 在 vm 中的响应式数据的属性名
          node.addEventListener('input', function (e) {
            vm[name] = e.target.value // 把输入的值给 vm 中的响应式数据 
          })
          node.value = vm.data[name] // 将 data 中的值赋给该 node
          node.removeAttribute('v-model')
        }
      }
    }
    // 如果是文本节点的话
    if (node.nodeType === 3) {
      if (reg.test(node.nodeValue)) {
        let name = RegExp.$1
        name = name.trim()
        new Watcher(vm, node, name) // 绑定一个订阅者
      }
    }
  }

  //  节点转换成文档碎片时每个文档都处理一下
  function nodeToFragment(node, vm) {
    const fragment = document.createDocumentFragment()
    let child
    while (child = node.firstChild) {
      compile(child, vm)
      fragment.appendChild(child)
    }
    return fragment
  }

  // Vue 构造函数
  function Vue(options) {
    this.data = options.data
    observe(this.data, this)
    const id = options.el
    const dom = nodeToFragment(document.getElementById(id), this)
    // 处理完所有节点后, 重新把内容放回去
    document.getElementById(id).appendChild(dom)
  }

  // 实现一个响应式监听属性的函数, 一旦有赋新值就发生变化
  function defineReactive(obj, key, val) {
    const dep = new Dep()
    Object.defineProperty(obj, key, {
      get: function () {
        if (Dep.target) {
          dep.addSub(Dep.target)
        }
        return val
      },
      set: function (newVal) {
        if (newVal === val) {
          return
        }
        val = newVal
        // 一旦更新立马通知
        dep.notify()
      }
    })
  }

  // 实现一个观察者, 对于每一个实例, 每一个属性值都进行观察
  function observe(obj, vm) {
    for (let key of Object.keys(obj)) {
      defineReactive(vm, key, obj[key])
    }
  }

  // Watcher 监视者
  function Watcher(vm, node, name) {
    Dep.target = this
    this.vm = vm
    this.node = node
    this.name = name
    this.update()
    Dep.target = null
  }
  Watcher.prototype = {
    update() {
      this.get()
      this.node.nodeValue = this.value
    },
    get() {
      this.value = this.vm[this.name] // 触发相应的 get
    }
  }

  // dep 构造函数
  function Dep() {
    this.subs = []
  }
  Dep.prototype = {
    addSub(sub) {
      this.subs.push(sub)
    },
    notify() {
      this.subs.forEach(function (sub) {
        sub.update()
      })
    }
  }

  const vm = new Vue({
    el: 'app',
    data: {
      text: '不知道是什么情况'
    }
  })
</script>

</html>
